fix(cli): interrupt running commands cleanly on Ctrl+C#1370
Merged
Conversation
The launcher (bin/cli.js) spawns the real CLI as a child with inherited stdio. Previously it was torn down by SIGINT before that child finished, so the child's final status printed after the shell prompt had already returned (and the child could be left running in the background). Wait briefly for the child to handle the interrupt itself and mirror its exit, so output stays ordered. The wait is bounded: if the child outlives a short grace period, or on a second Ctrl+C, SIGKILL it and exit with the conventional 128+signum code. The grace timer is unref'd, so a child that exits on its own is mirrored with no added latency.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 010d858. Configure here.
Addresses review feedback: re-raising the child's signal on the launcher
raced against `await spawnPromise` resolving and could leave the default
`process.exitCode` of 1, so Ctrl+C could report exit 1 instead of 130.
Exit explicitly with the conventional 128+signum code (resolved from
os.constants.signals) in the on('exit') signal branch instead.
Martin Torp (mtorp)
approved these changes
Jun 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
The launcher (
bin/cli.js) spawns the real CLI as a child process with inherited stdio. Previously the launcher was torn down bySIGINTbefore that child finished, so onCtrl+Cthe child's final status could print after the shell prompt had already returned — and the child could be left running in the background.This makes the launcher wait for the child to handle the interrupt itself and then mirror its exit, so output stays ordered.
How
SIGINT/SIGTERM, stay alive and let the child (which shares our process group and receives the signal directly) run its own teardown; exit by mirroring the child's real exit.SHUTDOWN_GRACE_MS, 3s), or on a secondCtrl+C,SIGKILLit and exit with the conventional128 + signumcode.unref'd, so a child that exits on its own is mirrored with no added latency — the cap only applies to a slow or wedged child.Notes
Note
Low Risk
Launcher-only process/signal plumbing in bin/cli.js; no auth, data, or business-logic changes, with bounded fallback via SIGKILL.
Overview
The CLI launcher (
bin/cli.js) now coordinates shutdown when you hit Ctrl+C or sendSIGTERM, instead of exiting before the spawned CLI child finishes.On the first signal it stays alive so the child (same process group, inherited stdio) can tear down and print final output in order; the parent then mirrors the child’s exit. If the child doesn’t exit within 3s, or you signal again, it SIGKILLs the child and exits with 130 (
SIGINT) or 143 (SIGTERM). The grace timer is unref’d so a normal fast exit isn’t delayed.When the child exits on a signal, the launcher removes its own handlers before re-raising so the parent actually terminates instead of swallowing the signal.
Reviewed by Cursor Bugbot for commit 010d858. Configure here.